/**
 * This file is a part of qloudgen-sec. 
 * You can redistribute qloudgen-sec and/or modify it under the terms of the Lesser GNU General Public License version 3. 
 * qloudgen-sec is distributed WITHOUT ANY WARRANTY. 
 *
 * See the Lesser GNU General Public License for more details at http://www.gnu.org/licenses/. 
 **/
package org.qloudgen.sec.cas.client.tomcat;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;

import org.apache.catalina.Session;
import org.apache.catalina.SessionEvent;
import org.apache.catalina.SessionListener;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import org.apache.catalina.LifecycleException;

import org.jasig.cas.client.session.SessionMappingStorage;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.qloudgen.sec.cache.CacheManager;
import org.qloudgen.sec.cas.client.session.SessionCallbackHandler;
import org.qloudgen.sec.cas.client.session.SessionLogoutHandler;
import org.qloudgen.sec.cas.client.session.CasSingleSignOutHandler;
import org.qloudgen.sec.cas.client.session.CasSessionMappingStorage;
import org.qloudgen.sec.util.ClassUtil;
import org.qloudgen.sec.util.ExceptionUtil;

/**
 * <p>Title: Tomcat Client Single Sign Out Handler Implementation</p>
 * <p>Description: Wrappered tomcat client single sign out handler.</p>
 * <p>Copyright: qloudgen/sec 2014</p>
 * @author <a href="mailto:hyq.dd.sun@gmail.com">glue.3M</a>
 */
public final class TomcatSingleSignOutHandler extends ValveBase {
	/**
	 * Log output.
	 */
	private final static Logger logger = LoggerFactory.getLogger( TomcatSingleSignOutHandler.class );
	/**
	 *
	 */
	private CasSingleSignOutHandler casSingleSignOutHandler = new CasSingleSignOutHandler();
	/**
	 *
	 */
	private String cacheManager = null;
	/**
	 *
	 */
	private String sessionHandler = null;
	/**
	 *
	 */
	private String contextPath = null;
	
	/**
	 * Default constructor.
	 */
	public TomcatSingleSignOutHandler() {
	}
	
	/**
	 * Sets current session logout handler.
	 *
	 * @param sessionHandler
	 */
	public void setSessionHandler(final String sessionHandler) {
		this.sessionHandler = sessionHandler;
	}
	
	/**
	 * Returns current session logout handler.
	 *
	 * @return String
	 */
	public String getSessionHandler() {
		return sessionHandler;
	}
	
	/**
	 * Sets current cache manager.
	 *
	 * @param cacheManager
	 */
	public void setCacheManager(final String cacheManager) {
		this.cacheManager = cacheManager;
	}
	
	/**
	 * Returns current cache manager.
	 *
	 * @return String
	 */
	public String getCacheManager() {
		return cacheManager;
	}
	
	/**
	 * Sets current context path.
	 *
	 * @param contextPath
	 */
	public void setContextPath(final String contextPath) {
		this.contextPath = contextPath;
	}
	
	/**
	 * Returns current context path.
	 *
	 * @return String
	 */
	public String getContextPath() {
		return contextPath;
	}
	
	/**
	 * Handler initialize.
	 *
	 * @throws LifecycleException
	 */
	@Override
	protected void initInternal() throws LifecycleException {
		super.initInternal();
		//
		CasSessionMappingStorage casSessionMappingStorage = new CasSessionMappingStorage();
		casSessionMappingStorage.setCacheManager( ( CacheManager )ClassUtil.getInstance( cacheManager ) );
		
		casSingleSignOutHandler.setSessionMappingStorage( casSessionMappingStorage );
		if ( sessionHandler != null ) {
			casSingleSignOutHandler.setSessionLogoutHandler( ( SessionLogoutHandler )ClassUtil.getInstance( sessionHandler ) );
		}
		casSingleSignOutHandler.init();
		
		logger.info( "Initialized current cas single sign out handler...{}" , this );
	}
	
	/**
	 * Handler start.
	 *
	 * @throws LifecycleException
	 */
	@Override
	protected void startInternal() throws LifecycleException {
		super.startInternal();
		logger.info( "Started current cas single sign out handler...{}" , this );
	}
	
	/**
	 * Handler stop.
	 *
	 * @throws LifecycleException
	 */
	@Override
	protected void stopInternal() throws LifecycleException {
		super.stopInternal();
		logger.info( "Stopped current cas single sign out handler...{}" , this );
	}
	
	/**
	 * Handler handle.
	 *
	 * @param request
	 * @param response
	 * throws IOException
	 * throws ServletException
	 */
	@Override
	public void invoke(final Request request , final Response response) throws IOException, ServletException {
		logger.trace( "Current handle request is...[{},{}]" , request.getRequestURI() , request );
		logger.trace( "Current handle response is...{}" , response );
		/**
		 * Do single sign out only for client context.
		 */
		if ( request.getContextPath().equals( contextPath ) ) {
			/**
			 * Do inject listener.
			 */
			addListener( request );
			/**
			 * Do record.
			 */
			if ( casSingleSignOutHandler.isTokenRequest( request ) ) {
				logger.debug( "Starting record user session..." );
				casSingleSignOutHandler.recordSession( request );
			}
			/**
			 * Do destroy and break handler chain to application.
			 */
			else if ( casSingleSignOutHandler.isLogoutRequest( request ) ) {
				logger.debug( "Starting destroy user session..." );
				casSingleSignOutHandler.destroySession( request , new TomcatSessionCallbackHandler( request ) );
				return;
			}
		}
		/**
		 * Do next handler finally.
		 */
		getNext().invoke( request , response );
	}
	
	/**
	 *
	 */
	private void addListener(Request request) {
		synchronized ( this ) {
			Session session = request.getSessionInternal( false );
			if ( session != null && session.getNote( "TomcatSingleSignOutListener" ) == null ) {
				SessionListener listener = new TomcatSingleSignOutListener();
				session.addSessionListener( listener );
				session.setNote( "TomcatSingleSignOutListener" , listener );
				//
				logger.trace( "Started add inernal session listener...{}" , listener );
			}
		}
	}
	
	/**
	 * Returns the String representation.
	 *
	 * @return String
	 */
	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append( "[" );
		builder.append( super.toString() );
		builder.append( "-->" );
		builder.append( "casSingleSignOutHandler=" ).append( casSingleSignOutHandler );
		builder.append( "]" );
		//
		return builder.toString();
	}
	
	/**
	 *
	 */
	private class TomcatSessionCallbackHandler implements SessionCallbackHandler {
		/**
		 *
		 */
		private Request request = null;
		
		/**
		 * Custom constructor.
		 *
		 * @param request
		 */
		public TomcatSessionCallbackHandler(Request request) {
			this.request = request;
		}
		
		/**
		 * Retrieve session by id.
		 *
		 * @param sessionId
		 * @return HttpSession
		 */
		public HttpSession getSessionById(String sessionId) {
			try {
				return request.getContext().getManager().findSession( sessionId ).getSession();
			}
			catch (Throwable t) {
				throw new RuntimeException( t );
			}
		}
	}

	/**
	 *
	 */
	public class TomcatSingleSignOutListener implements SessionListener {
		/**
		 * Session create event listen.
		 *
		 * @param event
		 */
		public void sessionEvent(SessionEvent event) {
			/**
			 * Session created.
			 */
			if ( Session.SESSION_CREATED_EVENT.equals( event.getType() ) ) {
				logger.info( "Current session is created...{}" , event.getSession().getId() );
			}
			/**
			 * Session deatroyed.
			 */
			else if ( Session.SESSION_DESTROYED_EVENT.equals( event.getType() ) ) {
				logger.info( "Current session is destroyed...{}" , event.getSession().getId() );
				try {
					/**
					 * Do get current session mapping storage.
					 */
					SessionMappingStorage storage = casSingleSignOutHandler.getSessionMappingStorage();
					/**
					 * Do really session remove.
					 */
					storage.removeBySessionById( event.getSession().getId() );
				}
				catch (Throwable t) {
					logger.error( "Current session destroyed is fail...\n{}" , ExceptionUtil.get( t ) );
				}
			}
		}
	}
}